package com.oly.compiler;


import com.google.auto.service.AutoService;
import com.oly.annotation.Generate;
import com.oly.annotation.TransDispose;
import com.oly.config.FileConfig;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;

@AutoService(Processor.class)
public class DataSourceProcessor extends AbstractProcessor {
    private Messager messager;
    Filer filer;
    JavaFile dataSourceFile;
    JavaFile remoteFile;
    private Elements mElementUtils;
    private FileConfig[] configs = {null, null, null};


    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(TransDispose.class.getCanonicalName());
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        filer = processingEnvironment.getFiler();
        messager = processingEnvironment.getMessager();
        mElementUtils = processingEnvironment.getElementUtils();
        configFiles();
    }

    final String sourcePackName = "com.oly.data.source";
    final String sourceSimpleClassName = "IDataSource";
    final String sourceFullClassName = sourcePackName + "." + sourceSimpleClassName;

    private void configFiles() {
        TypeSpec.Builder mInterface =
                TypeSpec.interfaceBuilder("IDataSource")
                        .addModifiers(Modifier.PUBLIC);

        configs[0] = new FileConfig(mInterface, sourcePackName, FileConfig.FileType.INTERFACE);

        ClassName superinterface = ClassName.bestGuess(sourceFullClassName);
        TypeSpec.Builder remote =
                TypeSpec.classBuilder("RemoteSource")
                        .addSuperinterface(superinterface)
                        .addModifiers(Modifier.PUBLIC);
        configs[1] = new FileConfig(remote, "com.oly.data.remote", FileConfig.FileType.REMOTE);
        TypeSpec.Builder repo =
                TypeSpec.classBuilder("DataRepository")
                        .addSuperinterface(superinterface)
                        .addModifiers(Modifier.PUBLIC);
        configs[2] = new FileConfig(repo, "com.oly.data", FileConfig.FileType.REPOSITORY);
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {

        if (dataSourceFile != null) {
            //messager.printMessage(Diagnostic.Kind.NOTE, "process:dataSourceFile created");
            return false;
        }
        Set<? extends Element> viewModelSet = roundEnv.getElementsAnnotatedWith(TransDispose.class);
        if (viewModelSet.size() <= 0) {
            // messager.printMessage(Diagnostic.Kind.NOTE, "process:no class inject");
            return false;
        }
        Set<? extends Element> generateSet = roundEnv.getElementsAnnotatedWith(Generate.class);

        if (viewModelSet.size() > 0) {
            if (generateSet.size() == 0) {
                messager.printMessage(Diagnostic.Kind.ERROR, prefix + "  \n----ERROR----   :  not found   anntation '@Generate'" + prefix);
                return false;
            }
            if (generateSet.size() > 1) {
                String name = "";
                for (Element element : generateSet) {
                    TypeElement e = (TypeElement) element;
                    name += e.getQualifiedName() + ",";
                }
                messager.printMessage(Diagnostic.Kind.ERROR, prefix + "  \n----ERROR----   :only one but   found  " + name + "anntate with @Generate" + prefix);
                return false;
            }
            for (Element element : generateSet) {
                if (!element.getKind().isInterface()) {
                    // annotate a non interface type is denied
                    TypeElement e = (TypeElement) element;
                    messager.printMessage(Diagnostic.Kind.ERROR, prefix + " \nERROR:\nannotate a non interface type is denied\n" +
                            e.getQualifiedName() + "  is not an interface"
                            + prefix);
                    return false;
                }
            }
        }


        processService(set, roundEnv);
        return false;
    }

    String prefix = "\n-------------------------------------------------------------------------------------------------";

    private void processService(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {

        for (FileConfig config : configs) {
            try {
                configFile(roundEnv, config);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    private void configFile(RoundEnvironment roundEnv, FileConfig config) {
        Set<? extends Element> annotatedWith = roundEnv.getElementsAnnotatedWith(TransDispose.class);
        List<MethodSpec> methodSpecList = new ArrayList<>();


        for (Element tp : annotatedWith) {
            ExecutableElement te = (ExecutableElement) tp;
            List<ParameterSpec> pList = new ArrayList<>();

            List<? extends VariableElement> parametersList = te.getParameters();
            for (int i = 0; i < parametersList.size(); i++) {
                VariableElement variableElement = parametersList.get(i);
                String simpleName = variableElement.getSimpleName().toString();
                TypeName typeName = TypeName.get(variableElement.asType());
                pList.add(ParameterSpec.builder(
                        typeName,
                        simpleName)
                        .build());
            }
            String type = te.getReturnType().toString();
            int start = type.indexOf("<");
            int end = type.lastIndexOf(">") + 1;

            TypeVariableName listener = TypeVariableName.get(
                    "com.chinaoly.sdk.data.DataCallBackListener" + type.substring(start, end));
            ParameterSpec spec = ParameterSpec.builder(listener, "listener").build();
            pList.add(spec);

            TypeName returnType = ClassName.bestGuess("io.reactivex.disposables.Disposable");


            MethodSpec.Builder builder = MethodSpec
                    .methodBuilder(tp.getSimpleName().toString())
                    .addModifiers(Modifier.PUBLIC)
                    .addParameters(pList)
                    .returns(returnType);
            if (FileConfig.FileType.INTERFACE == config.type) {
                builder.addModifiers(Modifier.ABSTRACT);
            } else if (FileConfig.FileType.REMOTE == config.type) {
                String method = "return transToDispose(service." + tp.getSimpleName().toString() + "(";
                for (int i = 0; i < parametersList.size(); i++) {
                    if (i < parametersList.size() - 1) {
                        method += parametersList.get(i) + ",";
                    } else {
                        method += parametersList.get(i);
                    }

                }
                method += "),listener)";
                builder.addStatement(method).addAnnotation(Override.class);
            } else {
                String method = "return source." + tp.getSimpleName().toString() + "(";
                for (int i = 0; i < parametersList.size(); i++) {
                    method += parametersList.get(i) + ",";
                }
                method += "listener)";
                builder.addStatement(method).addAnnotation(Override.class);
            }

            methodSpecList.add(builder.build());
            // messager.printMessage(Diagnostic.Kind.NOTE, "methodSpecList:" + methodSpecList.size());
        }
        if (FileConfig.FileType.REMOTE == config.type) {
            methodSpecList.add(getServiceConstruct(roundEnv));
            config.typeBuilder.addField(getServiceFieldSpec(roundEnv));
        } else if (FileConfig.FileType.REPOSITORY == config.type) {
            methodSpecList.add(getDataSourceConstruct());
            config.typeBuilder.addField(getDataSourceFieldSpec());
        }


        config.typeBuilder.addMethods(methodSpecList);
        config.fileBuilder = JavaFile.builder(config.packageName, config.typeBuilder.build())
                .addFileComment("File   auto generate ,do not edit\n\n");

        if (FileConfig.FileType.REMOTE == config.type) {
            ClassName name = ClassName.bestGuess("com.chinaoly.sdk.util.ObservableUtil");
            config.fileBuilder.addStaticImport(name, "transToDispose");
        }

        try {
            config.fileBuilder.build().writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private FieldSpec getServiceFieldSpec( RoundEnvironment roundEnv) {
        TypeElement generate = null;
        for (Element element : roundEnv.getElementsAnnotatedWith(Generate.class)) {
            generate = (TypeElement) element;
        }
        return FieldSpec.builder(ClassName.get(generate), "service")
                .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
                .build();
    }

    private FieldSpec getDataSourceFieldSpec() {
        return FieldSpec.builder(ClassName.bestGuess(sourceFullClassName), "source")
                .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
                .build();
    }

    private MethodSpec getServiceConstruct(RoundEnvironment roundEnv) {
        TypeElement generate = null;
        for (Element element : roundEnv.getElementsAnnotatedWith(Generate.class)) {
            generate = (TypeElement) element;
        }


        ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(generate), "service").build();
        return MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(parameterSpec)
                .addStatement("this.$N = $N", "service", "service")
                .build();
    }

    private MethodSpec getDataSourceConstruct() {

        ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.bestGuess(sourceFullClassName), "source").build();
        return MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(parameterSpec)
                .addStatement("this.$N = $N", "source", "source")
                .build();
    }
}
